/*
 *    Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: lengleng (wangiegie@gmail.com)
 */
package com.pig4cloud.pigx.system.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.okex.open.api.config.APIConfiguration;
import com.okex.open.api.customize.constant.ApiOkxConstant;
import com.okex.open.api.customize.enums.ApiOkxEnum;
import com.okex.open.api.customize.util.ApiOkxUtil;
import com.okex.open.api.service.publicData.impl.PublicDataAPIServiceImpl;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.exception.ApiRequestException;
import com.pig4cloud.pigx.common.customize.exception.DuckweedException;
import com.pig4cloud.pigx.common.customize.mysql.utils.MysqlUtil;
import com.pig4cloud.pigx.common.customize.redis.constants.RedisKeyEnum;
import com.pig4cloud.pigx.common.customize.redis.utils.RedisUtils;
import com.pig4cloud.pigx.common.customize.security.utils.SecurityUtil;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.strategy.api.feign.StrategyContractFeign;
import com.pig4cloud.pigx.system.constant.SystemPlatformConstant;
import com.pig4cloud.pigx.system.dto.SystemProductAddDTO;
import com.pig4cloud.pigx.system.dto.SystemProductEditDTO;
import com.pig4cloud.pigx.system.entity.SystemPlatform;
import com.pig4cloud.pigx.system.entity.SystemProduct;
import com.pig4cloud.pigx.system.enums.SystemProductEnum;
import com.pig4cloud.pigx.system.mapper.SystemProductMapper;
import com.pig4cloud.pigx.system.mapstruct.SystemProductStruct;
import com.pig4cloud.pigx.system.service.SystemProductService;
import com.pig4cloud.pigx.system.vo.SystemProductOptionVO;
import com.pig4cloud.pigx.system.vo.SystemProductVO;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 产品
 *
 * @since 2022-11-23 15:15:20
 */
@Service
@RequiredArgsConstructor
public class SystemProductServiceImpl extends ServiceImpl<SystemProductMapper, SystemProduct> implements SystemProductService {

    private final StrategyContractFeign strategyContractFeign;

    private final SecurityUtil securityUtil;
    private final RedisUtils redisUtils;
    private final MysqlUtil mysqlUtil;
    private final RedisTemplate<String, Object> redis;

    /**
     * 分页查询
     *
     * @param page      分页条件
     * @param condition 查询条件
     * @return {@link Page< SystemProductVO>}
     * @since 2022/11/24 10:48
     */
    @Override
    public Page<SystemProductVO> page(Page<SystemProduct> page, SystemProduct condition) {

        QueryWrapper<SystemProduct> query = new QueryWrapper<>();
        query.lambda()
                .eq(Strings.isNotBlank(condition.getType()), SystemProduct::getType, condition.getType())
                .like(Strings.isNotBlank(condition.getSymbolName()), SystemProduct::getSymbolName, condition.getSymbolName())
                .eq(!Objects.isNull(condition.getPlatformId()), SystemProduct::getPlatformId, condition.getPlatformId())
                .like(Strings.isNotBlank(condition.getName()), SystemProduct::getName, condition.getName())
                .orderByDesc(SystemProduct::getCreateTime);

        Page<SystemProduct> res = baseMapper.selectPage(page, query);
        List<SystemProduct> list = res.getRecords();
        List<SystemProductVO> voList = SystemProductStruct.INSTANCE.list2VOList(list);
        if (CollectionUtils.isEmpty(voList)) {
            return MysqlUtil.EMPTY_PAGE;
        }

        Set<Long> platformIdSet = voList.stream()
                .map(SystemProductVO::getPlatformId)
                .collect(Collectors.toSet());
        List<SystemPlatform> platformList = redisUtils.getPersistentCache(RedisKeyEnum.PLATFORM, platformIdSet);

        // 遍历
        voList.forEach(vo -> {
            // 设置平台名称
            for (SystemPlatform platform : platformList) {
                if (Objects.equals(platform.getId(), vo.getPlatformId())) {
                    vo.setPlatformName(platform.getName());
                    break;
                }
            }

            // 翻译字典项
            vo.setType(SystemProductEnum.Type.findName(vo.getType()));
        });

        return MysqlUtil.transferPage(page, voList);

    }

    /**
     * 新增
     *
     * @param dto 数据
     * @since 2022/11/24 11:31
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void add(SystemProductAddDTO dto) {

        SystemProduct entity = SystemProductStruct.INSTANCE.addDTO2entity(dto);
        PigxUser user = SecurityUtil.getLoginUserNotNull();
        LocalDateTime now = LocalDateTime.now();

        // 去对应的第三方平台查询数据
        entity.setSheetValue(getSheetValue(entity.getPlatformId(), entity.getName(), dto.getSymbolName()));
        entity.setCreateName(user.getName());
        entity.setUpdateName(user.getName());
        securityUtil.initCreateAbout(entity, user, now);

        if (baseMapper.add(entity) != 1) {
            // 查询平台名称
            SystemPlatform platform = redisUtils.getPersistentCache(RedisKeyEnum.PLATFORM, entity.getPlatformId(), SystemPlatform.class);

            String errorMsg = String.format("{%s}币对{%s}的{%s}下名为{%s}的产品数据已存在",
                    platform.getName(),
                    entity.getSymbolName(),
                    SystemProductEnum.Type.findName(entity.getType()),
                    entity.getName());
            throw new DuckweedException(errorMsg);
        }

        // 刷新缓存
        redisUtils.flushPersistentCache(RedisKeyEnum.PRODUCT, entity.getId(), entity);

    }

    /**
     * 修改
     *
     * @param dto 数据
     * @since 2022/11/24 11:31
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void edit(SystemProductEditDTO dto) {

        SystemProduct entity = baseMapper.selectById(dto.getId());
        if (Objects.isNull(entity)) {
            throw new DuckweedException("数据状态已变化");
        }

        // 检查引用
        checkReferences(entity.getId());

        entity = SystemProductStruct.INSTANCE.editDTO2entity(dto);
        PigxUser user = SecurityUtil.getLoginUserNotNull();
        LocalDateTime now = LocalDateTime.now();

        // 去对应的第三方平台查询数据
        entity.setSheetValue(getSheetValue(entity.getPlatformId(), entity.getName(), dto.getSymbolName()));
        entity.setUpdateName(user.getName());
        securityUtil.initUpdateAbout(entity, user, now);

        if (baseMapper.edit(entity) != 1) {
            // 查询平台名称
            SystemPlatform platform = redisUtils.getPersistentCache(RedisKeyEnum.PLATFORM, entity.getPlatformId(), SystemPlatform.class);

            String errorMsg = String.format("{%s}币对{%s}的{%s}下名为{%s}的产品数据已存在",
                    platform.getName(),
                    entity.getSymbolName(),
                    SystemProductEnum.Type.findName(entity.getType()),
                    entity.getName());
            throw new DuckweedException(errorMsg);
        }

        // 刷新缓存
        entity = baseMapper.selectById(entity.getId());
        redisUtils.flushPersistentCache(RedisKeyEnum.PRODUCT, entity.getId(), entity);

    }


    /**
     * 获取张数面值
     *
     * @param platformId  平台id
     * @param productName 产品名称
     * @return {@link BigDecimal}
     * @since 2022/12/3 19:14
     */
    public BigDecimal getSheetValue(Long platformId, String productName, String symbolName) {
        SystemPlatform platform = redisUtils.getPersistentCache(RedisKeyEnum.PLATFORM, platformId, SystemPlatform.class);
        if (Objects.isNull(platform) || Objects.equals(CommonDictEnum.Status.DEPRECATED.getValue(), platform.getStatus())) {
            throw new DuckweedException("平台信息已不可用");
        }

        if (SystemPlatformConstant.OKX == platformId) {
            return okxSheetValue(platform, productName);
        }
        return null;
    }

    /**
     * 欧易 张数面值
     *
     * @param platform    平台信息
     * @param productName 产品名称
     * @return r
     */
    public BigDecimal okxSheetValue(SystemPlatform platform, String productName) {
        APIConfiguration apiConfig = new APIConfiguration();
        apiConfig.setEndpoint(platform.getUrl());

        PublicDataAPIServiceImpl apiService = new PublicDataAPIServiceImpl(apiConfig);

        // 设置参数
        String[] split = productName.split("-");
        String uly = split[0] + "-" + split[1];

        JSONObject res = apiService.getInstruments(ApiOkxEnum.InstType.SWAP.getValue(), uly);
        // 检查是否成功
        try {
            ApiOkxUtil.isSuccess(res);
        } catch (ApiRequestException e) {
            String errorMsg = String.format("未能查询到产品 %s 的信息", productName);
            throw new DuckweedException(errorMsg);
        }

        JSONArray list = res.getJSONArray(ApiOkxConstant.DATA);
        if (list.isEmpty()) {
            String errorMsg = String.format("未能查询到产品 %s 的信息", productName);
            throw new DuckweedException(errorMsg);
        }
        return list.getJSONObject(0).getBigDecimal("ctVal");
    }

    /**
     * 根据id逻辑删除
     *
     * @param id id
     * @since 2022/11/24 11:31
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void del(Long id) {

        // 检查引用
        checkReferences(id);

        PigxUser user = SecurityUtil.getLoginUserNotNull();
        LocalDateTime now = LocalDateTime.now();

        UpdateWrapper<SystemProduct> update = new UpdateWrapper<>();
        update.lambda()
                .set(SystemProduct::getUpdateBy, user.getId())
                .set(SystemProduct::getUpdateName, user.getName())
                .set(SystemProduct::getUpdateTime, now)
                .set(SystemProduct::getIsDeleted, null)
                .eq(SystemProduct::getId, id);
        if (baseMapper.update(null, update) != 1) {
            throw new DuckweedException("操作失败");
        }

        // 刷新缓存
        redisUtils.delPersistentCache(RedisKeyEnum.PRODUCT, id);

    }

    /**
     * 检查引用
     *
     * @param id id
     * @since 2022/11/24 16:04
     */
    private void checkReferences(Long id) {

        // 检查在策略-合约表strategy_contract中的引用
        R<Long> strategyCountR = strategyContractFeign.countByProductId(id, SecurityConstants.FROM_IN);
        if (!strategyCountR.getIsSuccess()) {
            throw new DuckweedException("服务异常");
        }

        if (strategyCountR.getData() > 0) {
            throw new DuckweedException("该产品数据正在被某个策略使用");
        }

    }

    /**
     * 查询选项数据
     *
     * @param condition 查询条件
     * @return {@link List< SystemProductOptionVO>}
     * @since 2022/11/24 14:26
     */
    @Override
    public List<SystemProductOptionVO> option(SystemProduct condition) {

        QueryWrapper<SystemProduct> query = new QueryWrapper<>();
        query.lambda()
                .select(SystemProduct::getId,
                        SystemProduct::getName)
                .eq(Strings.isNotBlank(condition.getType()), SystemProduct::getType, condition.getType())
                .eq(!Objects.isNull(condition.getPlatformId()), SystemProduct::getPlatformId, condition.getPlatformId())
                .eq(Strings.isNotBlank(condition.getSymbolName()), SystemProduct::getSymbolName, condition.getSymbolName())
                .orderByAsc(SystemProduct::getName);
        List<SystemProduct> list = baseMapper.selectList(query);

        return SystemProductStruct.INSTANCE.list2OptionVOList(list);

    }

    /**
     * 初始化redis缓存
     */
    @Override
    public void initRedis() {
        try {
            mysqlUtil.startLocalTx();
            // 所有启用的币对
            List<SystemProduct> systemProducts = new SystemProduct().selectList(new QueryWrapper<SystemProduct>() {{
                last("for update");
            }});
            for (SystemProduct product : systemProducts) {
                redisUtils.flushPersistentCache(RedisKeyEnum.PRODUCT, product.getId(), product);
            }
            mysqlUtil.commitLocalTx();
        } catch (Exception e) {
            mysqlUtil.rollbackLocalTx();
        }
    }

    /**
     * 同步产品信息
     *
     * @param id id
     * @since 2023/1/28 15:48
     */
    @Override
    public void sync(Long id) {
        SystemProduct product = redisUtils.getPersistentCache(RedisKeyEnum.PRODUCT, id, SystemProduct.class);
        if (Objects.isNull(product)) {
            String errorMsg = String.format("未能查询到产品信息，id：%s", id);
            throw new DuckweedException(errorMsg);
        }

        BigDecimal sheetValue = getSheetValue(product.getPlatformId(), product.getName(), product.getSymbolName());
        product.setSheetValue(sheetValue);

        if (baseMapper.updateById(product) != 1) {
            String errorMsg = String.format("同步产品信息失败，id：%s", id);
            throw new DuckweedException(errorMsg);
        }

        // 刷新缓存
        redisUtils.flushPersistentCache(RedisKeyEnum.PRODUCT, product.getId(), product);
    }

    /**
     * 同步所有可用产品信息
     *
     * @since 2023/1/28 15:49
     */
    @Override
    public void syncAll() {
        Set<Object> keySet = redis.opsForHash().keys(RedisKeyEnum.PRODUCT.getPersistentCacheKey());
        keySet.forEach(key -> sync(Long.valueOf(key.toString())));
    }
}
